package loan.calc.config;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import loan.calc.constant.dict.base.BaseDictEnum;
import loan.calc.interceptor.WebContextInterceptor;
import loan.calc.util.DictEnumUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;

import java.io.IOException;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Configuration
@Slf4j
public class WebMvcConfiguration implements WebMvcConfigurer {

    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    @Override
    public void addInterceptors(@NonNull InterceptorRegistry registry) {
        //添加自定义拦截器，并模式匹配所以路径
        registry.addInterceptor(new WebContextInterceptor()).addPathPatterns("/**");
    }

    @Override
    public void configurePathMatch(@NonNull PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = Optional.ofNullable(configurer.getUrlPathHelper()).orElse(new UrlPathHelper());
        // 不移除分号后的内容（启用矩阵变量）
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer commonJackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            builder.findModulesViaServiceLoader(Boolean.TRUE);
            // 对Long类型处理
            builder.serializerByType(Long.class, ToStringSerializer.instance);
            builder.serializerByType(Long.TYPE, ToStringSerializer.instance);
            // 对日期时间处理
            builder.locale(Locale.getDefault());
            builder.timeZone(TimeZone.getDefault());
            builder.simpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
            builder.modulesToInstall(obtainJavaTimeModule());
            // 自定义枚举序列化器和反序列化器
            builder.serializerByType(Enum.class, enumSerializer());
            builder.deserializerByType(Enum.class, enumDeserializer());
        };
    }

    private JavaTimeModule obtainJavaTimeModule() {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT);
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT);
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT);
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));
        return javaTimeModule;
    }

    private <E extends Enum<E>> JsonSerializer<E> enumSerializer() {
        return new JsonSerializer<E>() {
            @Override
            public void serialize(E e, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                if (BaseDictEnum.class.isAssignableFrom(e.getClass())) {
                    jsonGenerator.writeString(((BaseDictEnum) e).getValue());
                    jsonGenerator.writeStringField(jsonGenerator.getOutputContext().getCurrentName()+"Desc",((BaseDictEnum) e).getDescription());
                } else {
                    Optional<Field> enumValueAnnotationField = BaseEnumConverter.findEnumValueAnnotationField(e.getClass());
                    if (enumValueAnnotationField.isPresent()) {
                        jsonGenerator.writeString((String)ReflectionUtils.getField(enumValueAnnotationField.get(), e));
                    } else {
                        jsonGenerator.writeString(e.toString());
                    }
                    Optional<Field> enumDescAnnotationField = BaseEnumConverter.findEnumDescAnnotationField(e.getClass());
                    if (enumDescAnnotationField.isPresent()) {
                        jsonGenerator.writeStringField(jsonGenerator.getOutputContext().getCurrentName() + "Desc", (String) ReflectionUtils.getField(enumValueAnnotationField.get(), e));
                    }
                }
            }
        };
    }

    @SuppressWarnings("rawtypes")
    private JsonDeserializer<Enum> enumDeserializer() {
        return new JsonDeserializer<Enum>() {
            @SuppressWarnings("unchecked")
            @Override
            public Enum deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException, JacksonException {
                JsonNode node = p.getCodec().readTree(p);
                String currentName = p.currentName();
                Object currentValue = p.getCurrentValue();
                Class findPropertyType;
                findPropertyType = BeanUtils.findPropertyType(currentName, currentValue.getClass());
                final String asText = node.asText();
                if (StringUtils.isBlank(asText) || (StringUtils.isNotBlank(asText) && "0".equals(asText))) {
                    return null;
                }
                if (BaseDictEnum.class.isAssignableFrom(findPropertyType)) {
                    return (Enum) DictEnumUtils.select(findPropertyType, asText);
                }
                Optional<Field> enumValueAnnotationField = BaseEnumConverter.findEnumValueAnnotationField(findPropertyType);
                if (enumValueAnnotationField.isPresent()) {
                    return (Enum) Arrays.stream(findPropertyType.getEnumConstants()).filter(x -> Objects.equals(ReflectionUtils.getField(enumValueAnnotationField.get(), x), asText)).findFirst().orElse(null);
                }
                return Enum.valueOf(findPropertyType, asText);
            }
        };
    }
}
